파이썬 (0518) 12주차

클래스
Author

김보람

Published

May 18, 2022

클래스 공부 3단계

- 클래스 오브젝트에 소속된 변수와 인스턴스오브젝트에 소속된 변수를 설명한다.

오브젝트의 개념

- 파이썬은 모든 것이 오브젝트로 이루어져 있다. <- 우선은 그냥 명언처럼 외우자

- 오브젝트는 메모리 주소에 저장되는 모든 것을 의미한다.

a=1
id(a)  # 메모리주소를 보는 명령어
7390560
a='asdf'
id(a)
139914601692912
a=[1,2,3]
id(a)
139914601744176

- 클래스와 인스턴스도 오브젝트이다.

class A:
    x=0
    def f(self):
        print(self.x)
  • A는 오브젝트
id(A)
37700928
  • a는 오브젝트
a=A()
id(a)
139914601844368
  • b는 오브젝트
b=A()
id(b)
139914601854160

- 앞으로는 A를 클래스오브젝트, a,b를 인스턴스 오브젝트라고 부르자.

예제1 : 클래스변수, 인스턴스변수

- 시점0

class A:
    x=0
    y=0
    def f(self):
        self.x=self.x + 1
        A.y = A.y + 1
        # self.y = self.y + 1 이렇게 안쓰고 위에처럼 써보자!
        print("현재 인스턴스에서 f가 {}번 실행".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행".format(self.y))
id(A)
38051088
A.x, A.y
(0, 0)

- 시점1

a= A()
b= A()
[A.x, A.y], [a.x, a.y], [b.x, b.y]
([0, 0], [0, 0], [0, 0])

- 시점2

a.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행
[A.x, A.y], [a.x, a.y], [b.x, b.y]
([0, 1], [1, 1], [0, 1])

- 시점3

b.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행
[A.x, A.y], [a.x, a.y], [b.x, b.y]
([0, 2], [1, 2], [1, 2])

- 시점4

b.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 3번 실행
[A.x, A.y], [a.x, a.y], [b.x, b.y]
([0, 3], [1, 3], [2, 3])

- 시점5

a.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 4번 실행
[A.x, A.y], [a.x, a.y], [b.x, b.y]
([0, 4], [2, 4], [2, 4])

- 시점6

c=A()
[A.x, A.y], [a.x, a.y], [b.x, b.y], [c.x, c.y]
([0, 4], [2, 4], [2, 4], [0, 4])

- 시점7

c.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 5번 실행
[A.x, A.y], [a.x, a.y], [b.x, b.y], [c.x, c.y]
([0, 5], [2, 5], [2, 5], [1, 5])

- 신기한점: 각 인스턴스에서 인스턴스이름.f()를 실행한 횟수를 서로 공유하는 듯 하다. (A가 관리하는 것처럼 느껴진다.)

- x와 y는 약간 느낌이 다르다. x는 지점소속, y는 본사소속의 느낌?

이 예제에서 x는 인스턴스오브젝트에 소속된 변수, y는 클래스 오브젝트에 소속된 변수처럼 느껴짐

(약속) 앞으로는 인스턴스 오브젝트에 소속된 변수를 인스턴스 변수라고 하고, 클래스 오브젝트에 소속된 변수를 클래스 변수라고 하자.

- 인스턴스 변수와 클래스 변수를 구분하는 방법? 인스턴스이름.__dict__를 쓰면 인스턴스 변수만 출력된다.

  • 따라서 a. + tab을 눌러서 나오는 변수중 a.__dict__에 출력되지 않으면 클래스 변수이다.
a.__dict__
{'x': 2}
b.__dict__
{'x': 2}
c.__dict__
{'x': 1}

- 이 예제에서 아래는 모두 클래스 변수이다.

a.y, b.y, c.y
(5, 5, 5)

예제2: 인스턴스에서 인스턴스 변수 x 변경 (변경 가능)

- 시점0

class A:
    x=0
    y=0
    def f(self):
        self.x=self.x + 1
        A.y = A.y + 1
        # self.y = self.y + 1 이렇게 안쓰고 위에처럼 써보자!
        print("현재 인스턴스에서 f가 {}번 실행".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행".format(self.y))
a=A()
[A.x, A.y], [a.x, a.y]
([0, 0], [0, 0])

- 시점1

a.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행
a.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행
a.f()
현재 인스턴스에서 f가 3번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 3번 실행
[A.x,A.y], [a.x, a.y]
([0, 3], [3, 3])

- 시점2

a.x = 0 # f의 실행기록을 초기화 하고 싶다.
a.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 4번 실행
[A.x,A.y], [a.x, a.y]
([0, 4], [1, 4])

예제3: 클래스에서 클래스 변수 y 변경 (변경가능)

class A:
    x=0
    y=0
    def f(self):
        self.x=self.x + 1
        A.y = A.y + 1
        # self.y = self.y + 1 이렇게 안쓰고 위에처럼 써보자!
        print("현재 인스턴스에서 f가 {}번 실행".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행".format(self.y))
a=A()
b=A()
[A.x, A.y], [a.x, a.y], [b.x, b.y]
([0, 0], [0, 0], [0, 0])

- 시점1

a.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행
[A.x, A.y], [a.x, a.y], [b.x, b.y]
([0, 1], [1, 1], [0, 1])

- 시점2

A.y = 100
[A.x, A.y], [a.x, a.y], [b.x, b.y]
([0, 100], [1, 100], [0, 100])
a.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 101번 실행

예제4: 클래스에서 클래스 변수 x 변경 (변경가능)

class A:
    x=0
    y=0
    def f(self):
        self.x=self.x + 1
        A.y = A.y + 1
        # self.y = self.y + 1 이렇게 안쓰고 위에처럼 써보자!
        print("현재 인스턴스에서 f가 {}번 실행".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행".format(self.y))
a=A()
[A.x, A.y], [a.x, a.y]
([0, 0], [0, 0])

- 시점1

a.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행
[A.x, A.y], [a.x, a.y]
([0, 1], [1, 1])

- 시점2

A.x = 100   # 이렇게 되면 앞으로 만들어진 인스턴스튼 기본적으로 현재 인스턴스에서| 100번 f를 실행하였다는 정보를 가지고 태어나게 된다.
[A.x, A.y], [a.x, a.y]
([100, 1], [1, 1])

- 시점3

b=A()
[A.x, A.y], [a.x, a.y], [b.x, b.y]
([100, 1], [1, 1], [100, 1])

- 시점4

b.f()
현재 인스턴스에서 f가 101번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행

- 시점5

a.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 3번 실행
a.f()
현재 인스턴스에서 f가 3번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 4번 실행
b.f()
현재 인스턴스에서 f가 102번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 5번 실행

예제4의 변형

class B:
    x=100   # 초기자본금
    y=0
    def f(self):   # f를 실행할때마다 돈을 쓴다.
        self.x=self.x - 1
        B.y = B.y + 1
        # self.y = self.y + 1 이렇게 안쓰고 위에처럼 써보자!
        print("현재 인스턴스에서 {}원 잔액남음".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 총 {}원 사용".format(self.y))
a=B()
b=B()
[B.x, B.y], [a.x, a.y], [b.x, b.y]
([100, 0], [100, 0], [100, 0])

- 시점1

a.f() # 돈을 쓴다
현재 인스턴스에서 99원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 1원 사용
a.f()
현재 인스턴스에서 98원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 2원 사용
b.f()
현재 인스턴스에서 99원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 3원 사용

- 시점2

[B.x, B.y], [a.x, a.y], [b.x, b.y]
([100, 3], [98, 3], [99, 3])
B.x=999
[B.x, B.y], [a.x, a.y], [b.x, b.y]
([999, 3], [98, 3], [99, 3])

- 시점3

c=B()
c.f()
현재 인스턴스에서 998원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 4원 사용

- 시점4

a.f()
현재 인스턴스에서 97원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 5원 사용
b.f()
현재 인스턴스에서 98원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 6원 사용
c.f()
현재 인스턴스에서 997원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 7원 사용
c.f()
현재 인스턴스에서 996원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 8원 사용

예제5 : 인스턴스에서 클래스변수 변경 (변경가능???)

class A:
    x=0
    y=0
    def f(self):
        self.x=self.x + 1
        A.y = A.y + 1
        # self.y = self.y + 1 이렇게 안쓰고 위에처럼 써보자!
        print("현재 인스턴스에서 f가 {}번 실행".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행".format(self.y))
[A.x, A.y]
[0, 0]
a=A()
b=A()
[A.x, A.y], [a.x, a.y], [b.x,b.y]
([0, 0], [0, 0], [0, 0])

- 시점1

a.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행
b.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행
a.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 3번 실행

- 시점2

a.__dict__
{'x': 2}
a.y  # 인스턴스 a에 소속되어 있지만 클래스 변수 
3
a.y = 999 # A.y 였으면 다 바꼈을 테지만 a.y 였다면??
# 내가 하드코딩으로 a.y에 999 입력 -> 이것이 A.y나 b.y에도 반영될까? (x)
[A.x, A.y], [a.x, a.y], [b.x,b.y]
([0, 3], [2, 999], [1, 3])
a.__dict__
{'x': 4, 'y': 999}

- 시점3

b.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 4번 실행
a.f()
현재 인스턴스에서 f가 3번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 999번 실행
b.f()
현재 인스턴스에서 f가 3번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 6번 실행
b.f()
현재 인스턴스에서 f가 4번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 7번 실행
a.f()
현재 인스턴스에서 f가 4번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 999번 실행

- 요약 - 인스턴스에서 클래스 변수의 값을 변경하면? -> 클래스변수의 값이 변경되는 것이 아니라 인스턴스 변수가 새롭게 만들어져서 할당 된다. - 이 예제에서 a.y는 이제 클래스변수에서 인스턴스 변수로 재탄생 되었다. 즉, 999오브젝트가 새롭게 만들어져서 a.x라는 이름을 얻은것이다. - 기존의 A.y나 b.y에는 아무런 변화가 없다.

id(999) #999도 오브젝트임
139914476320048

a.y = 999 는 새로운 인스턴스 변수 y를 할당하는 역할을 한다. 클래스변수의 값을 변경하는 것이 아니다. (왜냐하면 애초에 a.y는 없는 값이었고, A.y를 빌리고 있었던 것임)

a.__dict__
{'x': 4, 'y': 999}
b.__dict__
{'x': 4}

예제5의 변형

class A:
    x=0
    y=0
    def f(self):
        self.x=self.x + 1
        A.y = A.y + 1
        # self.y = self.y + 1 이렇게 안쓰고 위에처럼 써보자!
        print("현재 인스턴스에서 f가 {}번 실행 (인스턴스레벨)".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행 (클레스레벨)".format(A.y))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행 (인스턴스레벨)".format(self.y))
a=A()
a.f()
현재 인스턴스에서 f가 1번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행 (클레스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행 (인스턴스레벨)
b=A()
b.f()
현재 인스턴스에서 f가 1번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행 (클레스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행 (인스턴스레벨)

- 시점1

a.y = 999
a.f()
현재 인스턴스에서 f가 2번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 3번 실행 (클레스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 999번 실행 (인스턴스레벨)
a.f()
현재 인스턴스에서 f가 3번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 4번 실행 (클레스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 999번 실행 (인스턴스레벨)

예제6: 인스턴스 생성시점에 대한 분석

- 의문: 아래의 코드에서 x는 클래스 변수라고 봐야할까? 인스턴스 변수라고 봐야할까? —> 클래스 변수!

class SoWhaTV: 
    x=0   # 이 시점에서 x는 클래스변수인가? 아니면 인스턴스 변수인가?
    def f(self):
        print(self.x)

- 시점0

class A:
    x=0
    y=0
    def f(self):
        self.x=self.x + 1
        A.y = A.y + 1
        # self.y = self.y + 1 이렇게 안쓰고 위에처럼 써보자!
        print("현재 인스턴스에서 f가 {}번 실행 (인스턴스레벨)".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행 (클레스레벨)".format(A.y))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행 (인스턴스레벨)".format(self.y))
a=A()
b=A()
a.x, a.y, b.x, b.y
(0, 0, 0, 0)
a.__dict__, b.__dict__
({}, {})
  • 지금 시점에서 a.x, a.y, b.x, b.y는 모두 클래스 변수임

- 시점1

a.f()
현재 인스턴스에서 f가 1번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행 (클레스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행 (인스턴스레벨)
a.__dict__, b.__dict__
({'x': 1}, {})
  • 이 순간 a.x가 클래스변수에서 인스턴스 변수로 변경되었다. (예제5와 같이..) 왜? f가 실행되면서 self.x = self.x + 1이 실행되었으므로!

- 시점2

b.f()
현재 인스턴스에서 f가 1번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행 (클레스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행 (인스턴스레벨)
a.__dict__, b.__dict__
({'x': 1}, {'x': 1})

예제7: 잘못된 사용

- 아래처럼 코드를 바꾸면 어떻게 되는가?

class A:
    def __init__(self):
        self.x=0 # 인스턴스 변수로 나중에 쓸꺼니까 명시함
        A.y=0  # 클래스변수로 나중에 쓸꺼니까 명시함
    def f(self):
        self.x=self.x + 1
        A.y = A.y + 1
        # self.y = self.y + 1 이렇게 안쓰고 위에처럼 써보자!
        print("현재 인스턴스에서 f가 {}번 실행 (인스턴스레벨)".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행 (클레스레벨)".format(A.y))
        #print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행 (인스턴스레벨)".format(self.y))

- 사용

a=A()
b=A()
a.f()
현재 인스턴스에서 f가 1번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행 (클레스레벨)
b.f()
현재 인스턴스에서 f가 1번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행 (클레스레벨)
b.f()
현재 인스턴스에서 f가 2번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 3번 실행 (클레스레벨)
b.f()
현재 인스턴스에서 f가 3번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 4번 실행 (클레스레벨)
a.f()
현재 인스턴스에서 f가 2번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 5번 실행 (클레스레벨)

- 잘 되는 것 같다?

- 조금만 생각해보면 엉터리라는 것을 알 수 있다. 아래를 관찰하자.

c=A()   # 이 시점에서 __init__()이 실행된다
a.f()
현재 인스턴스에서 f가 3번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행 (클레스레벨)
  • 클래스 레벨의 변수가 왜 초기화가 되었지?

- 오류의 이유? c=A()가 실행되는 시점에 __init__()이 실행되면서 A.y=0이 실행된다. 따라서 강제 초기화가 진행되었다.